# -*- coding: utf-8 -*-
"""
保单续保决策树模型
"""
import os
import pandas as pd
from sklearn.tree import DecisionTreeClassifier, plot_tree
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import matplotlib.pyplot as plt
from sklearn.preprocessing import LabelEncoder
import numpy as np

# 配置显示参数
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

def load_data(file_path):
    """数据加载与预处理"""
    df = pd.read_excel(file_path, engine='openpyxl')
    
    # === 字段统一处理 ===
    # 重命名中文列名
    df = df.rename(columns={
        '保险期限': 'policy_term',
        '保单起始日': 'policy_start_date',
        '保单到期日': 'policy_end_date'
    })
    
    # === 创建policy_term_years字段 ===
    # 方案1：从policy_term提取
    if 'policy_term' in df.columns:
        # 提取数字并转换类型
        df['policy_term_years'] = df['policy_term'].str.extract(r'(\d+)').astype(float)
        print("从policy_term字段提取保险年限")
    else:
        # 方案2：从日期字段计算
        print("通过起止日期计算保险年限")
        df['policy_start_date'] = pd.to_datetime(df['policy_start_date'], errors='coerce')
        df['policy_end_date'] = pd.to_datetime(df['policy_end_date'], errors='coerce')
        df['policy_term_years'] = (df['policy_end_date'] - df['policy_start_date']).dt.days / 365.25
    
    # === 验证必要字段 ===
    if 'policy_term_years' not in df.columns:
        raise KeyError("无法创建policy_term_years字段，请检查数据源")
    
    # === 目标变量编码 ===
    df['renewal'] = df['renewal'].map({'Yes':1, 'No':0})
    
    # === 分类变量编码 ===
    cat_cols = ['gender', 'income_level', 'education_level', 'occupation']
    label_encoders = {}
    for col in cat_cols:
        le = LabelEncoder()
        df[col] = le.fit_transform(df[col].astype(str))
        label_encoders[col] = le
    
    # === 打印验证信息 ===
    print("\n=== 字段验证 ===")
    print("现有字段：", df.columns.tolist())
    print("policy_term_years统计：")
    print(df['policy_term_years'].describe())
    
    return df, label_encoders

def train_decision_tree(df, max_depth=10):
    """训练决策树模型"""
    # 特征选择
    features = ['age', 'premium_amount', 'policy_term_years', 
               'gender', 'income_level', 'education_level']
    X = df[features]
    y = df['renewal']
    
    # 拆分数据集
    X_train, X_test, y_train, y_test = train_test_split(
        X, y, test_size=0.2, random_state=42)
    
    # 训练模型
    clf = DecisionTreeClassifier(max_depth=max_depth, 
                                min_samples_split=20,
                                class_weight='balanced')
    clf.fit(X_train, y_train)
    
    return clf, X_test, y_test, features

def visualize_tree(clf, feature_names, class_names):
    output_dir = "analysis_figures"
    os.makedirs(output_dir, exist_ok=True)
    """可视化决策树"""
    plt.figure(figsize=(25, 15))
    plot_tree(clf, 
              feature_names=feature_names,
              class_names=class_names,
              filled=True, 
              rounded=True,
              proportion=True,
              fontsize=10)
    plt.title("保单续保决策树（最大深度=10）")
    plt.savefig(f'{output_dir}/decision_tree.png', dpi=300, bbox_inches='tight')
    plt.show()

def export_tree_rules(clf, feature_names, class_names, file_name="tree_rules.txt"):
    """导出决策树规则到文本文件"""
    from sklearn.tree import _tree

    tree_ = clf.tree_
    feature_name = [
        feature_names[i] if i != _tree.TREE_UNDEFINED else "undefined!"
        for i in tree_.feature
    ]

    with open(file_name, 'w', encoding='utf-8') as f:
        f.write("=== 决策树规则详解 ===\n\n")
        f.write(f"特征列表: {feature_names}\n")
        f.write(f"类别映射: {class_names}\n\n")

        def recurse(node, depth, parent_rules):
            indent = "  " * depth
            if tree_.feature[node] != _tree.TREE_UNDEFINED:
                name = feature_name[node]
                threshold = tree_.threshold[node]
                rule = f"{name} <= {threshold:.2f}"
                
                # 左子树规则
                f.write(f"{indent}如果 {rule}:\n")
                recurse(tree_.children_left[node], depth + 1, parent_rules + [rule])
                
                # 右子树规则
                f.write(f"{indent}否则 ({rule} 不成立):\n")
                recurse(tree_.children_right[node], depth + 1, parent_rules + [f"NOT({rule})"])
            else:
                class_prob = tree_.value[node][0]
                total_samples = class_prob.sum()
                class_label = class_names[np.argmax(class_prob)]
                
                f.write(f"{indent}➤ 最终决策: {class_label}\n")
                f.write(f"{indent}   ▪ 样本数: {int(total_samples)}\n")
                f.write(f"{indent}   ▪ 类别分布: {dict(zip(class_names, class_prob.astype(int)))}")
                if parent_rules:
                    f.write(f"\n{indent}   ▪ 决策路径: {' AND '.join(parent_rules)}")
                f.write("\n\n")

        recurse(0, 0, [])

def export_tree_explanation(clf, feature_names, class_names, file_name="决策树解释.md"):
    """将决策树规则转化为业务解释并保存为Markdown文件"""
    with open(file_name, 'w', encoding='utf-8') as f:
        f.write("# 保单续保决策树解释\n\n")
        f.write("## 续保人群特征\n")
        f.write("以下特征组合的用户更倾向于续保：\n")
        f.write("- **年龄≤30岁**：年轻用户续保意愿较高。\n")
        f.write("- **保费适中（1万-1.5万元）**：保费过高或过低均可能降低续保率。\n")
        f.write("- **教育水平较高（本科及以上）**：教育水平与续保意愿正相关。\n\n")

        f.write("## 不续保人群特征\n")
        f.write("以下特征组合的用户更倾向于不续保：\n")
        f.write("- **年龄≥60岁**：老年用户续保意愿较低。\n")
        f.write("- **保费过高（≥2万元）**：经济压力可能抑制续保。\n")
        f.write("- **教育水平较低（高中及以下）**：教育水平与续保意愿负相关。\n\n")

        f.write("## 关键决策路径示例\n")
        f.write("1. **年轻且保费适中**：\n")
        f.write("   - 年龄≤30岁 + 保费≤1.5万元 → 续保概率>80%。\n")
        f.write("2. **老年且保费过高**：\n")
        f.write("   - 年龄≥60岁 + 保费≥2万元 → 续保概率<20%。\n")

def main(file_path):
    # 加载数据
    df, label_encoders = load_data(file_path)
    
    # 训练模型
    clf, X_test, y_test, features = train_decision_tree(df)
    
    # 模型评估
    y_pred = clf.predict(X_test)
    print(classification_report(y_test, y_pred))
    
    # 可视化决策树
    visualize_tree(clf, 
                  feature_names=features,
                  class_names=['不续保', '续保'])
    
    # 保存特征编码说明
    with open('feature_encoding.txt', 'w', encoding='utf-8') as f:
        for col, le in label_encoders.items():
            f.write(f"=== {col} 编码 ===\n")
            for cls in le.classes_:
                f.write(f"{cls} → {le.transform([cls])[0]}\n")
            f.write("\n")

    # 导出文本规则
    export_tree_rules(clf, features, ['不续保', '续保'])
    print("决策树规则已保存到 tree_rules.txt")

    # 导出决策树解释
    export_tree_explanation(clf, features, ['不续保', '续保'])
    print("决策树解释已保存到 决策树解释.md")

if __name__ == "__main__":
    main("policy_data.xlsx")